package com.autumn.workflow.service.impl;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipInputStream;

import org.activiti.engine.ProcessEngine;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.history.HistoricVariableInstance;
import org.activiti.engine.impl.interceptor.Command;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.Execution;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StreamUtils;

import com.autumn.platform.core.Utils;
import com.autumn.platform.core.exception.Throws;
import com.autumn.workflow.WorkflowConsts;
import com.autumn.workflow.command.ParamCommand;
import com.autumn.workflow.command.diagram.DiagramCommand;
import com.autumn.workflow.command.diagram.ProcessTraceCommand;
import com.autumn.workflow.command.task.FreedomTaskCompleteCommand;
import com.autumn.workflow.command.task.InitializeUserTaskNodeAndFlowCommand;
import com.autumn.workflow.command.task.QueryNextPossibleUserTaskNodesCommand;
import com.autumn.workflow.command.task.FreedomTaskCompleteCommand.FreedomTaskCompleteParam;
import com.autumn.workflow.model.ProcessTrace;
import com.autumn.workflow.model.UserTaskFlow;
import com.autumn.workflow.model.UserTaskNode;
import com.autumn.workflow.service.WorkflowService;

public class ActivitiWorkflowService implements WorkflowService, ApplicationContextAware {

    @Autowired
    private ProcessEngine engine;

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    /**
     * 执行命令
     */
    @Override
    @Transactional
    public <T> T execute(final Command<T> command) {
        return this.engine.getManagementService().executeCommand(command);
    }

    /**
     * 执行含参数的命令
     */
    @Override
    @Transactional
    public <P, T> T execute(final ParamCommand<P, T> command, final P param) {
        return this.execute(new Command<T>() {
            @Override
            public T execute(CommandContext commandContext) {
                return command.execute(commandContext, param);
            }
        });
    }

    /**
     * 执行含参数的命令
     */
    @Override
    @Transactional
    public <P, T> T execute(final Class<? extends ParamCommand<P, T>> commandClass, final P param) {
        ParamCommand<P, T> command = null;
        try {
            command = this.applicationContext.getBean(commandClass);
        } catch (NoSuchBeanDefinitionException e) {
            try {
                // 如果Spring容器中未注册，尝试直接根据类型创建命令实例
                command = commandClass.newInstance();
            } catch (Exception e1) {
                throw e;//尝试失败，抛出找不到bean的原异常
            }
        }
        return this.execute(command, param);
    }

    @SuppressWarnings("unchecked")
    @Override
    @Transactional
    public <P, T> T execute(final String commandBeanname, final P param) {
        ParamCommand<P, T> command = null;
        try {
            command = this.applicationContext.getBean(commandBeanname, ParamCommand.class);
        } catch (NoSuchBeanDefinitionException e) {
            try {
                // 如果Spring容器中未注册，尝试直接根据类名创建命令实例
                Class<? extends ParamCommand<P, T>> commandClass = (Class<? extends ParamCommand<P, T>>) Class.forName(commandBeanname);
                command = commandClass.newInstance();
            } catch (Exception e1) {
                throw e;//尝试失败，抛出找不到bean的原异常
            }
        }
        return this.execute(command, param);
    }

    /**
     * 发布类路径下的流程定义
     */
    @Override
    @Transactional
    public Deployment deployProcessDefinition(String classpathResource, String userId) {
        if (Utils.isBlank(userId)) {
            Throws.throwException(WorkflowConsts._500001);
        }
        Deployment deployment = this.engine.getRepositoryService().createDeployment().name(userId).addClasspathResource(classpathResource).deploy();
        initializeUserTaskNodeAndFlowByDeploymentId(deployment.getId());
        return deployment;
    }

    /**
     * 发布压缩文件zip流中的流程定义
     */
    @Override
    @Transactional
    public Deployment deployProcessDefinition(ZipInputStream zipInputStream, String userId) {
        if (Utils.isBlank(userId)) {
            Throws.throwException(WorkflowConsts._500001);
        }
        Deployment deployment = this.engine.getRepositoryService().createDeployment().name(userId).addZipInputStream(zipInputStream).deploy();
        initializeUserTaskNodeAndFlowByDeploymentId(deployment.getId());
        return deployment;
    }

    /**
     * 发布流程定义
     * 
     * @param resourceName 资源名称
     * @param inputStream 流程定义输入流
     * @param userId 发布人员ID
     */
    @Override
    @Transactional
    public Deployment deployProcessDefinition(String resourceName, InputStream inputStream, String userId) {
        if (Utils.isBlank(userId)) {
            Throws.throwException(WorkflowConsts._500001);
        }
        Deployment deployment = this.engine.getRepositoryService().createDeployment().name(userId).addInputStream(resourceName, inputStream).deploy();
        initializeUserTaskNodeAndFlowByDeploymentId(deployment.getId());
        return deployment;
    }

    /**
     * 发布流程定义
     * 
     * @param resourceName 资源名称
     * @param bytes 流程定义字节数组
     * @param userId 发布人员ID
     */
    @Override
    @Transactional
    public Deployment deployProcessDefinition(String resourceName, byte[] bytes, String userId) {
        if (Utils.isBlank(userId)) {
            Throws.throwException(WorkflowConsts._500001);
        }
        Deployment deployment = this.engine.getRepositoryService().createDeployment().name(userId).addBytes(resourceName, bytes).deploy();
        initializeUserTaskNodeAndFlowByDeploymentId(deployment.getId());
        return deployment;
    }

    private void initializeUserTaskNodeAndFlowByDeploymentId(String deploymentId) {
        List<ProcessDefinition> definitions = this.engine.getRepositoryService().createProcessDefinitionQuery().deploymentId(deploymentId).list();
        if (null != definitions) {
            for (ProcessDefinition definition : definitions) {
                this.initializeUserTaskNodeAndFlow(definition.getId());
            }
        }
    }

    /**
     * 根据流程定义Id初始化任务
     */
    @Override
    public void initializeUserTaskNodeAndFlow(String procDefId) {
        this.execute(InitializeUserTaskNodeAndFlowCommand.class, procDefId);
    }

    /**
     * 根据流程定义Key查询流程实例
     */
    @Override
    public ProcessDefinition queryProcessDefinition(String procDefKey) {
        return this.engine.getRepositoryService().createProcessDefinitionQuery().processDefinitionKey(procDefKey).latestVersion().singleResult();
    }

    @Override
    public ProcessInstance startProcessInstanceByKey(String procDefKey, String businessKey, String startUserId) {
        return this.startProcessInstanceByKey(procDefKey, null, businessKey, startUserId);
    }

    /**
     * 启动流程实例
     */
    @Override
    public ProcessInstance startProcessInstanceByKey(String procDefKey, Map<String, Object> variables, String businessKey, String startUserId) {
        if (Utils.isBlank(startUserId)) {
            Throws.throwException(WorkflowConsts._500001);
        }
        if (Utils.isBlank(businessKey)) {
            Throws.throwException(WorkflowConsts._500002);
        }
        // 设置流程创建人
        this.engine.getIdentityService().setAuthenticatedUserId(startUserId);
        // 调用工作流API创建流程实例
        ProcessInstance processInstance = this.engine.getRuntimeService().startProcessInstanceByKey(procDefKey, businessKey, variables);
        return processInstance;
    }

    @Override
    public ProcessInstance startProcessInstanceById(String procDefId, String businessKey, String startUserId) {
        return this.startProcessInstanceById(procDefId, null, businessKey, startUserId);
    }

    /**
     * 启动流程实例
     */
    @Override
    public ProcessInstance startProcessInstanceById(String procDefId, Map<String, Object> variables, String businessKey, String startUserId) {
        if (Utils.isBlank(startUserId)) {
            Throws.throwException(WorkflowConsts._500001);
        }
        if (Utils.isBlank(businessKey)) {
            Throws.throwException(WorkflowConsts._500002);
        }
        // 设置流程创建人
        this.engine.getIdentityService().setAuthenticatedUserId(startUserId);
        // 调用工作流API创建流程实例
        ProcessInstance processInstance = this.engine.getRuntimeService().startProcessInstanceById(procDefId, businessKey, variables);
        return processInstance;
    }

    @Override
    public Map<String, Object> queryProcessInstanceVariables(String procInstId) {
        List<HistoricVariableInstance> list = this.engine.getHistoryService().createHistoricVariableInstanceQuery().processInstanceId(procInstId).list();
        if (null != list) {
            Map<String, Object> map = new HashMap<String, Object>();
            for (HistoricVariableInstance var : list) {
                map.put(var.getVariableName(), var.getValue());
            }
            return map;
        }
        return null;
    }

    /**
     * 查询当前活动节点列表
     * 
     * @param procInstId
     * @return
     */
    @Override
    public String queryCurrentNodeId(String procInstId) {
        List<String> nodes = queryCurrentNodeIds(procInstId);
        if (null == nodes || nodes.isEmpty()) {
            return null;
        } else if (nodes.size() > 1) {
            throw Throws.createException(WorkflowConsts._500009, nodes.size(), nodes);
        } else {
            return nodes.get(0);
        }
    }

    /**
     * 查询当前活动节点列表
     * 
     * @param procInstId
     * @return
     */
    @Override
    public List<String> queryCurrentNodeIds(String procInstId) {
        List<Execution> list = this.engine.getRuntimeService().createExecutionQuery().processInstanceId(procInstId).list();
        if (null != list) {
            List<String> nodes = new ArrayList<>();
            for (Execution execution : list) {
                String node = execution.getActivityId();
                if (!Utils.isBlank(node)) {
                    nodes.add(node);
                }
            }
            return nodes;
        }
        return null;
    }

    /**
     * 流程是否结束
     * 
     * @param procInstId
     * @return
     */
    @Override
    public boolean isEnd(String procInstId) {
        HistoricProcessInstance instance = this.engine.getHistoryService().createHistoricProcessInstanceQuery().processInstanceId(procInstId).singleResult();
        return null != instance.getEndTime();
    }

    /**
     * 领取任务，会检查是否已经被其他人领取
     */
    @Override
    @Transactional
    public void claimByTaskId(String taskId, String userId) {
        if (Utils.isBlank(userId)) {
            Throws.throwException(WorkflowConsts._500001);
        }
        Task task = this.engine.getTaskService().createTaskQuery().taskId(taskId).singleResult();
        if (null == task) {
            Throws.throwException(WorkflowConsts._500001, taskId);
        }
        String assignee = task.getAssignee();
        if (assignee == null) {
            this.engine.getTaskService().claim(taskId, userId);
            task.setAssignee(userId);
        } else if (userId.equals((assignee))) {
        } else {
            Throws.throwException(WorkflowConsts._500003, assignee);
        }
    }

    /**
     * 放弃任务
     */
    @Override
    public void unclaimByTaskId(String taskId) {
        this.engine.getTaskService().unclaim(taskId);
    }

    /**
     * 改变任务领取人，不会检查是否已经被其他人领取
     */
    @Override
    public void changeAssigneeByTaskId(String taskId, String userId) {
        if (Utils.isBlank(userId)) {
            Throws.throwException(WorkflowConsts._500001);
        }
        this.engine.getTaskService().setAssignee(taskId, userId);
    }

    /**
     * 查询下一步所有可能的任务节点
     */
    @Override
    public List<UserTaskNode> queryNextPossibleUserTaskNodes(String procDefId, String srcTaskKey, int action) {
        UserTaskFlow param = new UserTaskFlow();
        param.setAction(action);
        param.setProcDefId(procDefId);
        param.setSrcTaskKey(srcTaskKey);
        return this.execute(QueryNextPossibleUserTaskNodesCommand.class, param);
    }

    /**
     * 完成任务，先领取任务（如果需要的话），再完成任务
     */
    @Override
    @Transactional
    public void completeByTaskId(String taskId, String userId) {
        this.completeByTaskId0(taskId, null, userId, -1, null, null);
    }

    @Override
    @Transactional
    public void completeByTaskId(String taskId, String userId, String remark) {
        this.completeByTaskId0(taskId, null, userId, -1, null, remark);
    }

    @Override
    @Transactional
    public void completeByTaskId(String taskId, int action, String userId) {
        Map<String, Object> variables = new HashMap<>();
        variables.put("action", action);
        this.completeByTaskId0(taskId, variables, userId, -1, null, null);
    }

    @Override
    @Transactional
    public void completeByTaskId(String taskId, int action, String userId, String remark) {
        Map<String, Object> variables = new HashMap<>();
        variables.put("action", action);
        this.completeByTaskId0(taskId, variables, userId, -1, null, remark);
    }

    @Override
    @Transactional
    public void completeByTaskId(String taskId, Map<String, Object> variables, String userId) {
        this.completeByTaskId0(taskId, variables, userId, -1, null, null);
    }

    @Override
    @Transactional
    public void completeByTaskId(String taskId, Map<String, Object> variables, String userId, String remark) {
        this.completeByTaskId0(taskId, variables, userId, -1, null, remark);
    }

    @Override
    @Transactional
    public void completeFreedomByTaskId(String taskId, int action, String userId, String destTaskKey) {
        this.completeByTaskId0(taskId, null, userId, action, destTaskKey, null);
    }

    @Override
    @Transactional
    public void completeFreedomByTaskId(String taskId, int action, String userId, String destTaskKey, String remark) {
        this.completeByTaskId0(taskId, null, userId, action, destTaskKey, remark);
    }

    /**
     * 完成任务
     */
    private void completeByTaskId0(String taskId, Map<String, Object> variables, String userId, int action, String destTaskKey, String remark) {
        this.claimByTaskId(taskId, userId);
        FreedomTaskCompleteParam param = new FreedomTaskCompleteParam();
        param.setTaskId(taskId);
        param.setVariables(variables);
        param.setUserId(userId);
        param.setAction(action);
        param.setDestTaskKey(destTaskKey);
        param.setRemark(remark);
        this.execute(FreedomTaskCompleteCommand.class, param);
    }

    /**
     * 根据流程定义Key加载流程图
     */
    @Override
    public byte[] loadResourceByProcDefKey(String procDefKey) {
        ProcessDefinition processDefinition = this.queryProcessDefinition(procDefKey);
        if (null != processDefinition) {
            return this.loadResourceByProcDefId(processDefinition.getId());
        }
        return null;
    }

    /**
     * 根据流程定义ID加载流程图
     */
    @Override
    public byte[] loadResourceByProcDefId(String procDefId) {
        try {
            InputStream inputStream = this.engine.getRepositoryService().getProcessDiagram(procDefId);
            return StreamUtils.copyToByteArray(inputStream);
        } catch (IOException e) {
            throw Throws.createException(WorkflowConsts._500005, e.getMessage());
        }
    }

    /**
     * 根据流程实例ID加载流程图
     */
    @Override
    public byte[] loadResourceByProcInstId(String procInstId) {
        //        ProcessInstance processInstance = this.engine.getRuntimeService().createProcessInstanceQuery().processInstanceId(procInstId).singleResult();
        //        return this.loadResourceByProcDefId(processInstance.getProcessDefinitionId());
        return this.execute(DiagramCommand.class, procInstId);
    }

    @Override
    public ProcessTrace loadProcessTrace(String procInstId) {
        return this.execute(ProcessTraceCommand.class, procInstId);
    }
}
